Introducción y exploración de los datos


Presentación del problema

El presente documento presenta un hipotético caso real de proyecto de minería de datos. En el proyecto, se tratará de aplicar diversas técnicas y algoritmos de tratamiento de datos, con el objetivo de obtener reglas y conocimiento que pueda servir para el negocio del cliente.

Todo parte de una breve presentación del cliente…

Hola, me llamo Juan Schlemmer. Soy CEO a la cadena de tiendas presenciales Gourmet. Nos dedicamos al negocio de la alimentación y bebida de calidad. Tenemos presencia en tiendas en Europa y Estados Unidos. El negocio no nos va mal, pero me gustaría poder aumentar la facturación de la empresa. No tengo claro si aumentando las ventas o reduciendo gastos … El otro día jugando al golf con un compañero que se dedica al negocio de las comunicaciones en comentó que había puesto en marcha un proyecto de … Big Data o Minería de Datos o no sé qué del conocimiento … no lo tengo claro … Me dijo que le aconsejaron acciones concretas a partir de datos recogidos de su negocio y que está contento … La cuestión es que os enviarán unos datos extraídos de nuestro ERP para que me hagáis esto de la minería. Por favor si tenéis dudas con los datos preguntar a nuestros técnicos. ¿Cuándo podré tener un primer informe? ¿Dentro de un mes? Perfecto …

A partir de esto, se analizan los datos proporcionados y se trata de dar respuesta a las inquietudes del cliente que, en un primer momento, son expresadas de manera algo vaga. Se propondrán diversas soluciones de negocio a partir del conocimiento adquirido.

Objetivos

Mediante modelos de tres tipos distintos, se pretende extraer conocimiento que repercuta en un mayor beneficio para el negocio. Se usarán los siguientes tipos de modelos:

  • Agrupamiento o clustering.
  • Reglas de asociación.
  • Clasificación.

Importación de datos

library(tidyverse)
library(lubridate)
library(kableExtra)

Comenzamos importando los datos de los que dispone el cliente. Para nombrar las columnas de los conjuntos de datos, se utilizan los nombres presentes en el fichero .db2 proporcionado por el cliente.

#### IMPORTACION de datos -----------------


#cliente

cliente <- read.csv("cliente.csv", encoding = 'UTF-8', header = FALSE)

names(cliente) <- c("cod.cliente", "nombre.cliente", "sexo", "fecha.nacimiento", 
                    "estado.civil", "direccion", "profesion", "numero.hijos", "region", 
                    "nacionalidad", "total.compras", "puntos.acumulados")

  
# tienda --- rel con pais
  
tienda <- read.csv("tienda.csv", encoding = 'UTF-8', header = FALSE)

names(tienda) <- c("nombre", "direccion", "superficie", "formato.tienda", "pais", "tipo.zona")


# pais

pais <- read.csv("pais.csv", encoding = 'UTF-8', header = FALSE)

names(pais) <- c("nombre.pais", "extension", "poblacion" , "nombre.region")


# proveedor

proveedor <- read.csv("proveedor.csv", encoding = 'UTF-8', header = FALSE)

names(proveedor) <- c("cod.proveedor", "nombre.proveedor", "persona.contacto" , 
                      "direccion","telefono","periodo.pago", 
                      "pago.pendiente", "alcance")



# producto ----- relacionada con proveedor

producto <- read.csv("producto.csv", encoding = 'UTF-8', header = FALSE)

names(producto) <- c("cod.producto", "descripcion", "nombre.pais" , 
                      "coste","precio.venta","tipo.unidad", 
                      "nombre.subfamilia", "marca", "cod.proveedor" )




# seccion 

seccion <- read.csv("seccion.csv", encoding = 'UTF-8', header = FALSE)

names(seccion) <- c("nombre.seccion", "descripcion")


# familia -- rel seccion

familia <- read.csv("familia.csv", encoding = 'UTF-8', header = FALSE)

names(familia) <- c("nombre.familia", "descripcion", "nombre.seccion")




# subfamilia - rel con familia

subfamilia <- read.csv("subfamilia.csv", encoding = 'UTF-8', header = FALSE)

names(subfamilia) <- c("nombre.subfamilia", "descripcion", "nombre.familia")







# cabecera.ticket ---------- rel con cliente

cabecera.ticket <- read.csv("cabeceraticket.csv", encoding = 'UTF-8', header = FALSE)

names(cabecera.ticket) <- c("cod.venta", "nombre.tienda", "fecha" , 
                      "hora","forma.pago","cod.cliente", "importe.total", 
                       "total.unidades", "puntos.ticket")



# lineas.ticket ---------- rel con venta, producto, tienda, cabecera, promocion

lineas.ticket <- read.csv("lineasticket.csv", encoding = 'UTF-8', header = FALSE)

names(lineas.ticket) <- c("cod.linea", "cod.venta", "nombre.tienda" , 
                            "cod.producto","cantidad","precio.venta", 
                            "nombre.promocion", "cod.cabecera")



# promocion ---------- rel con seccion, producto, tienda, pais, familia, region

promocion <- read.csv("promocion.csv", encoding = 'UTF-8', header = FALSE)

names(promocion) <- c("nombre.promocion", "tipo.promocion", "coste" , 
                          "fecha.inicio","fecha.fin","cod.producto", 
                          "nombre.familia", "nombre.seccion", "nombre.tienda", 
                      "nombre.region", "nombre.pais")




# pedido ---------- rel con producto, tienda

pedido <- read.csv("pedido.csv", encoding = 'UTF-8', header = FALSE)

names(pedido) <- c("cod.pedido", "nombre.tienda", "cod.producto" , 
                      "precio.compra","cantidad.solicitada","fecha.solicitud", 
                      "cantidad.entregada", "fecha.entrega")

Explicación de los datos y las variables incluídas

  • cliente contiene datos de los clientes que han comprado en la cadena. Hay datos tanto de clientes particulares como de empresas. Guarda datos de interacción con la tienda, como número de compras o puntos acumulados y otros datos relativos al cliente: nacionalidad, número de hijos, estado civil, profesión o dirección.

  • producto almacena datos sobre los diversos productos a la venta en la cadena: país de origen, coste, precio de venta, marca o código del proveedor que lo suministra, entre otros.

  • pais hace referencia a los países de procedencia de los productos que se venden en las tiendas.

  • pedido contiene datos sobre los pedidos que se han realizado en las diferentes tiendas, con detalles como la fecha de solicitud y entrega o sobre los productos entregados.

  • tienda guarda información sobre las diversas tiendas con las que cuenta la cadena.

  • seccion incluye las cinco secciones en las que se clasifican los productos vendidos en la cadena: Vinos, Espumosos, Licores, Quesos o Postres.

  • familia se refiere a las diferentes clasificaciones posibles dentro de cada sección de productos.

  • subfamilia referencia las diversas clasificaciones dentro de cada familia de productos.

  • proveedor contiene información sobre los diversos comercios de los cuales la cadena compra los productos.

  • promocion incluye datos sobre las promociones llevadas a cabo en las distintas tiendas. No obstante, apenas cuenta con cinco valores.

  • cabecera.ticket guarda los valores de los tickets de compra que se crean con las transacciones con los clientes de la tienda. Incluye información como la tienda, la fecha y hora, la forma de pago, el total de unidades o el importe total de la compra.

  • lineas.ticket se refiere a las distintas lineas que componen los tickets de compra, es decir, a los distintos productos que se incluyen en cada una de las compras.

A continuación, se muestran las 12 tablas de partida.

Vista de las tablas

Clientes
cliente
Productos
producto
Países de los productos
pais
Pedidos
pedido
Tiendas
tienda
Secciones
seccion
Familias de productos
familia
Subfamilias de productos
subfamilia
Proveedores
proveedor
Promociones
promocion
Cabeceras de ticket
cabecera.ticket
Líneas de ticket
lineas.ticket

Limpieza de datos

Transformamos los valores de las columnas de fechas de formato character a formato date, mediante el paquete lubridate.

# Limpieza pedido --------------------





pedido$fecha.solicitud <- ymd(pedido$fecha.solicitud)

pedido$fecha.entrega <- ymd(pedido$fecha.entrega)


# Limpieza cliente --------------------


cliente$fecha.nacimiento <- ymd(cliente$fecha.nacimiento)



# Limpieza cabecera.ticket --------------------



cabecera.ticket$fecha <- ymd(cabecera.ticket$fecha)




# Limpieza promocion --------------------


promocion$fecha.inicio <- ymd(promocion$fecha.inicio)
promocion$fecha.fin <- ymd(promocion$fecha.fin)

Apreciamos el resultado efectuando summary en la tabla cliente:

summary(cliente)
##    cod.cliente          nombre.cliente      sexo      fecha.nacimiento    
##  0000001R:   1   Hut Pizzeria  :   6   Empresa: 805   Min.   :1910-02-04  
##  0000002J:   1   Norma         :   6   Hombre :2076   1st Qu.:1939-02-25  
##  0000003B:   1   Payne Henry   :   6   Mujer  :1188   Median :1951-11-09  
##  0000004N:   1   Brigham Ernest:   5                  Mean   :1952-02-17  
##  0000005E:   1   Noma          :   5                  3rd Qu.:1967-08-11  
##  0000006C:   1   Tanner        :   5                  Max.   :1979-12-31  
##  (Other) :4063   (Other)       :4036                                      
##         estado.civil                                     direccion   
##               : 805   116 Sussex Gardens ,London EC1          :   7  
##  Casado/a     :1318   15 Bury St, St James'S ,London NW1      :   7  
##  Divorciado/a : 651   Corso Buenos Aires 3 , Milano           :   7  
##  Soltero/a    :1219   Piazzale Suppercortemaggiore 4 , Milano :   7  
##  Viudo/a      :  76   Via Ludovico Ariosto 22 , Milano        :   7  
##                       Viale Lombardia 55 , Milano             :   7  
##                       (Other)                                 :4027  
##                                   profesion    numero.hijos   
##  Economistas,Abogados & Admin.Empresas :707   Min.   :0.0000  
##  Gerentes & Directivos                 :703   1st Qu.:0.0000  
##  Doctores & Profesionales de la Salud  :663   Median :0.0000  
##  Ingenieros & Especialistas            :507   Mean   :0.9684  
##  Architectos,Decoradores & Humanistas  :485   3rd Qu.:2.0000  
##  Catering                              :339   Max.   :4.0000  
##  (Other)                               :665   NA's   :805     
##           region             nacionalidad  total.compras   
##  Norte Europa:1821   España        :1349   Min.   : 0.000  
##  Norteamérica: 899   Estados Unidos: 899   1st Qu.: 5.000  
##  Sur Europa  :1349   Reino Unido   :1821   Median : 8.000  
##                                            Mean   : 8.729  
##                                            3rd Qu.:12.000  
##                                            Max.   :48.000  
##                                                            
##  puntos.acumulados
##  Min.   :  5.00   
##  1st Qu.:  7.00   
##  Median :  9.00   
##  Mean   : 11.54   
##  3rd Qu.: 12.00   
##  Max.   :105.00   
## 

Unión de la tabla cliente con la tabla producto

Nos proponemos saber que productos ha comprado cada cliente, para poder llevar a cabo un análisis más preciso del comportamiento de los clientes en la tienda. Para llevar a cabo dicha unión, debemos preparar los datos un poco más.

Detectamos que hay discrepancias entre levels de las variables cuantitativas o factor cod.producto y cod. cliente en las distintas tablas. Debemos hacer que los levels sean iguales, es decir, eliminar espacios en blanco. De no hacerlo, como consecuencia, al efectuar los join, obtendríamos muchos valores NA.

### imprimimos levels

# hay 
# 

lvls <- sort(unique(c(levels(cliente$cod.cliente), 
                      levels(cabecera.ticket$cod.cliente))))


lvls.prod <- sort(unique(c(levels(producto$cod.producto), 
                           levels(lineas.ticket$cod.producto))))
### Detectamos espacios en blanco en los lvls de cliente cabecera.ticket$cod.cliente, 
# los eliminamos

levels(cabecera.ticket$cod.cliente)<- str_trim(levels(cabecera.ticket$cod.cliente))

# idem para lineas.ticket$cod.producto

levels(lineas.ticket$cod.producto)<- str_trim(levels(lineas.ticket$cod.producto))
# transformamos en string los codigos cliente y producto
cliente$cod.cliente <- as.character(cliente$cod.cliente) 
cabecera.ticket$cod.cliente <- as.character(cabecera.ticket$cod.cliente)


producto$cod.producto <- as.character(producto$cod.producto) 
lineas.ticket$cod.producto <- as.character(lineas.ticket$cod.producto)


# nombre.subfamilia

producto$nombre.subfamilia <- as.character(producto$nombre.subfamilia) 
subfamilia$nombre.subfamilia <- as.character(subfamilia$nombre.subfamilia)

Procedemos con la unión de la tabla de cliente con la de producto. Para ello, deberemos realizar un recorrido desde cliente, pasando por cabecera ticket y lineas ticket, como se muestra en el siguiente bloque de código.

cl_prod <- cliente %>% left_join(cabecera.ticket, by = "cod.cliente") %>%
  inner_join(lineas.ticket, by = "cod.venta") %>%
  left_join(producto, by = "cod.producto") 

# agregamos la variable beneficio
cl_prod <- cl_prod %>% mutate(beneficio = precio.venta.y - coste)


cl_prod

Se ha agregado una variable nueva a la tabla cliente producto. Esta variable corresponde al beneficio que la tienda obtiene como resultado de la interacción con el cliente, en el proceso de venta de cada uno de los productos que este ha comprado. Esta variable será utilizada en los análisis y algoritmos posteriores.

Exploración de los datos

A continuación, se realiza una exploración gráfica de los datos, que nos permitirá tener un mayor conocimiento de las distintas variables que manejamos.

Visualizaciones de variables de la tabla cliente producto

Transacciones

Se incluye un análisis de las transacciones que han tenido lugar en la cadena.

cl_prod %>%
  mutate(hora = as.factor(hora)) %>%
  group_by(hora) %>%
  summarise(clientes = n_distinct(cod.cliente)) %>%
  ggplot(aes(x=hora, y=clientes)) +
  geom_bar(stat="identity", fill="steelblue3", show.legend=FALSE) +
  geom_label(aes(label=clientes)) +
  labs(title="Clientes por hora") + scale_x_discrete(labels = as.character(9:22), breaks = 9:22 )

Se observa como las horas con mayor número de clientes son de las 20 a las 22.

En la siguiente gráfica, el 1 representa el domingo y el 7 el sábado.

  cl_prod %>%
    mutate(dia.semana = as.factor(wday(fecha))) %>%
    group_by(dia.semana) %>%
    summarise(clientes = n_distinct(cod.cliente)) %>%
    ggplot(aes(x=dia.semana, y=clientes)) +
    geom_bar(stat="identity", fill="steelblue3", show.legend=FALSE) +
    geom_label(aes(label=clientes)) +
    labs(title="Clientes por día de semana") 

Vemos que los días de la semana con más clientes son los viernes y sábados.

# forma de pago
  
  cl_prod %>% 
    group_by(forma.pago) %>%
    summarise(count = length(forma.pago)) %>%
    ggplot(aes(x=forma.pago, y=count)) +
    geom_bar(stat="identity", fill="steelblue3", show.legend=FALSE) +
    geom_label(aes(label=count)) +
    labs(title="Forma de pago") + theme(axis.text.x = element_text(angle = 45, hjust = 1))

Los medios de pago más empleados son las tarjetas (crédito, débito y otras) con cerca de 50k pagos. Le siguen los cheques, con casi 30k y el pago en efectivo, con cerca de 20k.

Clientes

Se incluye un análisis de los clientes que compran en la cadena.

# genero de cliente
  
  cl_prod %>% 
    group_by(sexo) %>%
    summarise(count = length(sexo)) %>%
    filter(sexo != "Empresa") %>%
    ggplot(aes(x=sexo, y=count)) +
    geom_bar(stat="identity", fill="sienna2", show.legend=FALSE) +
    geom_label(aes(label=count)) +
    labs(title="Genero") 

Se aprecia que la mayoría de los clientes del establecimiento son hombres.

# profesión del cliente
  
  cl_prod %>% 
    group_by(profesion) %>%
    summarise(count = length(profesion)) %>%
    ggplot(aes(x=profesion, y=count)) +
    geom_bar(stat="identity", fill="sienna2", show.legend=FALSE) +
    labs(title="Profesión de los clientes") + theme(axis.text.x = element_text(angle = 45, hjust = 1))

Entre los grupos con mayor número de clientes, observamos un gran número de clientes que se dedican al sector de la alimentación y también otro grupo dedicado al mundo empresarial (economistas y gerentes).

# nacionalidad cliente
  
  cl_prod %>% 
    group_by(nacionalidad) %>%
    summarise(count = length(nacionalidad)) %>%
    ggplot(aes(x=nacionalidad, y=count)) +
    geom_bar(stat="identity", fill="sienna2", show.legend=FALSE) +
    geom_label(aes(label=count)) +
    labs(title="Nacionalidad de los clientes") + theme(axis.text.x = element_text(angle = 45, hjust = 1))

Los clientes son únicamente de 3 nacionalidades distintas, destacando el Reino Unido, con más de 40k clientes.

# numero de clientes y empresas
cl_prod %>%
    mutate(empresa_dummy = ifelse(sexo == 'Empresa' , 'Empresa', 'Cliente Particular')) %>%
    group_by(empresa_dummy) %>%
    summarise(count = n_distinct(cod.cliente)) %>%
    ggplot(aes(x=empresa_dummy, y=count)) +
    geom_bar(stat="identity", fill="sienna2", show.legend=FALSE) +
    geom_label(aes(label=count)) +
    labs(title="Número de clientes particulares y empresas")

La cadena cuenta con 3142 clientes particulares y 781 empresas.

Producto

Se incluye un análisis de los productos que vende la cadena.

# nombre.pais procedencia producto
  
  cl_prod %>% 
    group_by(nombre.pais) %>%
    summarise(count = length(nombre.pais)) %>%
    ggplot(aes(x=nombre.pais, y=count)) +
    geom_bar(stat="identity", fill="skyblue1", show.legend=FALSE) +
    labs(title="País de procedencia de los productos") + theme(axis.text.x = element_text(angle = 45, hjust = 1))

Observamos como una gran cantidad de productos provienen de España y Francia.

# productos-- nombre.subfamilia
  
  cl_prod %>% left_join(subfamilia, by = "nombre.subfamilia") %>% 
    left_join(familia, by = "nombre.familia") %>%
    group_by(nombre.seccion) %>%
    summarise(count = length(nombre.seccion)) %>% 
    filter (count > 3000) %>%
    ggplot(aes(x=nombre.seccion, y=count)) +
    geom_bar(stat="identity", fill="skyblue3", show.legend=FALSE) +
    labs(title="Secciones de los productos") + theme(axis.text.x = element_text(angle = 45, hjust = 1))

Los productos que más vende la tienda son vinos y quesos. Esto podría explicar la gran cantidad de productos españoles y franceses (países con grandes vinos y quesos).

Beneficio

Se muestran valores de beneficio por hora y por día de la semana.

cl_prod %>%
    mutate(hora = as.factor(hora)) %>%
    group_by(hora) %>%
    summarise(beneficio = sum(beneficio)) %>%
    ggplot(aes(x=hora, y=beneficio)) +
    geom_bar(stat="identity", fill="palegreen1", show.legend=FALSE) +
    labs(title="Beneficio por hora") + scale_x_discrete(labels = as.character(9:22), breaks = 9:22 ) 

El mayor beneficio, en el cómputo global, se obtiene sobre las 21 horas.

  cl_prod %>%
    mutate(dia.semana = as.factor(wday(fecha))) %>%
    group_by(dia.semana) %>%
    summarise(beneficio = sum(beneficio)) %>%
    ggplot(aes(x=dia.semana, y=beneficio)) +
    geom_bar(stat="identity", fill="palegreen1", show.legend=FALSE) +
    geom_label(aes(label=beneficio)) +
    labs(title="Beneficio por día de semana") 

Se aprecia como los días dónde el beneficio es mayor son el viernes y el sábado.

En general, los valores del beneficio están altamente relacionados con los valores del número de transacciones. Esto es algo que cabe esperar, pues cuantas más transacciones se produzcan, más probable será que la empresa aumente los beneficios.

Nos preguntamos si hay alguna diferencia del beneficio obtenido por empresas o clientes.

# beneficio: cliente y empresa
cl_prod %>%
    mutate(empresa_dummy = ifelse(sexo == 'Empresa' , 'Empresa', 'Cliente Particular')) %>%
    group_by(empresa_dummy) %>%
    summarise(beneficio = sum(beneficio)) %>%
    ggplot(aes(x=empresa_dummy, y=beneficio)) +
    geom_bar(stat="identity", fill="palegreen1", show.legend=FALSE) +
    geom_label(aes(label=beneficio)) +
    labs(title="Beneficio total por tipo de cliente")

El beneficio total es superior para clientes. No obstante, sabemos que hay muchos más clientes particulares que empresas. Por tanto, para obtener una medida que compare de manera más justa ambos grupos, mostramos el beneficio medio obtenido con cada uno.

# beneficio: cliente y empresa
cl_prod %>%
    mutate(empresa_dummy = ifelse(sexo == 'Empresa' , 'Empresa', 'Cliente Particular')) %>%
    group_by(empresa_dummy) %>%
    summarise(beneficio = mean(beneficio)) %>%
    ggplot(aes(x=empresa_dummy, y=beneficio)) +
    geom_bar(stat="identity", fill="palegreen1", show.legend=FALSE) +
    geom_label(aes(label=round(beneficio, digits = 2))) +
    labs(title="Beneficio medio por tipo de cliente")

Por cada empresa se obtiene un beneficio en media ligeramente superior al obtenido para clientes particulares.

Tras el análisis inicial de los datos, procedemos a la construcción de los modelos anteriormente mencionados.